package xproto

import (
	"fmt"
	"log"
	"net"
	"sync"

	"github.com/dontls/xproto/internal"
)

// Serve 服务
type Server struct {
	listener *net.TCPListener //
	wg       sync.WaitGroup   //
	adapters map[string]AdapterHandler
	Handler  Handler
	mpClis   map[string]*Conn
	lock     sync.RWMutex
	Options
	*rResult
}

var defaultAdapters = map[string]AdapterHandler{}

func Register(key string, h AdapterHandler) {
	if _, ok := defaultAdapters[key]; ok {
		panic("the protocol type is already registered")
	}
	defaultAdapters[key] = h
}

func (s *Server) Register(key string, handler AdapterHandler) {
	s.adapters[key] = handler
}

func (s *Server) addConn(c *Conn) {
	s.lock.Lock()
	s.mpClis[c.RemoteAddress] = c
	s.lock.Unlock()
}

func (s *Server) deleteConn(c *Conn) {
	s.lock.Lock()
	delete(s.mpClis, c.RemoteAddress)
	s.lock.Unlock()
}

var Default = NewServer(&Options{RequestTimeout: 60})

// NewServe 默认服务
func NewServer(opts *Options) *Server {
	s := &Server{Options: *opts, adapters: map[string]AdapterHandler{}}
	if s.Host == "" {
		s.Host = internal.LocalIPAddr()
	}
	if s.RecvTimeout == 0 {
		s.RecvTimeout = 30
	}
	if s.RequestTimeout == 0 {
		s.RequestTimeout = 10
	}
	s.rResult = &rResult{reqTimeout: opts.RequestTimeout}
	return s
}

// ListenTCPAndServe start server
func (s *Server) ListenTCPAndServe() error {
	tcpAddr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf(":%d", s.Port)) //获取一个tcpAddr
	listener, err := net.ListenTCP("tcp", tcpAddr)
	log.Println("xproto server listening at", tcpAddr.String(), err)
	if err != nil {
		return err
	}
	if len(s.adapters) < 1 {
		s.adapters = defaultAdapters
	}
	s.listener = listener
	s.mpClis = make(map[string]*Conn)
	defer s.listener.Close()
	for {
		conn, err := listener.Accept()
		if err != nil {
			return err
		}
		if c := newConn(conn, s); c != nil {
			s.wg.Add(1)
			s.addConn(c)
			go func() {
				c.start()
				s.wg.Done()
				s.deleteConn(c)
			}()
		}
	}
}

// Release 关闭服务
func (s *Server) Release() {
	if s.listener != nil {
		s.listener.Close()
	}
	s.lock.RLock()
	for _, c := range s.mpClis {
		c.Close()
	}
	s.lock.RUnlock()
	s.wg.Wait()
}
